/*   Copyright 2012 Julian Aloofi

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include "GameScreen.hpp"

Tacticus::GameScreen::GameScreen() : placingNow(NOBODY), boardElements(
    std::vector<std::vector<boost::shared_ptr<SpriteElement> > >(10,
    std::vector<boost::shared_ptr<SpriteElement> >(10, boost::shared_ptr<
    SpriteElement>()))), sidebarElements(
    std::vector<std::vector<boost::shared_ptr<SpriteElement> > >(2,
    std::vector<boost::shared_ptr<SpriteElement> >(6, boost::shared_ptr<
    SpriteElement>()))), sidebarPieces(std::vector<std::vector<Piece> >(2,
    std::vector<Piece>(6, Piece())))
{
    sidebarPieces[0][0] = MARSHAL;
    sidebarPieces[1][0] = GENERAL;
    sidebarPieces[0][1] = COLONEL;
    sidebarPieces[1][1] = MAJOR;
    sidebarPieces[0][2] = CAPTAIN;
    sidebarPieces[1][2] = LIEUTENANT;
    sidebarPieces[0][3] = SERGEANT;
    sidebarPieces[1][3] = MINER;
    sidebarPieces[0][4] = SCOUT;
    sidebarPieces[1][4] = SPY;
    sidebarPieces[0][5] = BOMB;
    sidebarPieces[1][5] = FLAG;

    boardBackgroundElement = boost::shared_ptr<SpriteElement>(
        new SpriteElement(DisplayLayers::BACKGROUND, ResourceManager::
        getInstance().getSprite("BoardBackground")));
    addScreenElement(boardBackgroundElement);

    sidebarBackgroundElement = boost::shared_ptr<SpriteElement>(
        new SpriteElement(DisplayLayers::BACKGROUND, ResourceManager::
        getInstance().getSprite("SidebarBackground")));
    sidebarBackgroundElement->setPosition(WindowManager::getInstance().
        toScreenCoordinate(Common::Coordinate(10.f, 0.f)));
    addScreenElement(sidebarBackgroundElement);

    sidebarGridElement = boost::shared_ptr<SpriteElement>(
        new SpriteElement(DisplayLayers::NORMAL, ResourceManager::
        getInstance().getSprite("SidebarGrid")));
     sidebarGridElement->setPosition(WindowManager::getInstance().
        toScreenCoordinate(Common::Coordinate(10.f, 3.f)));
    addScreenElement(sidebarGridElement);

    doneButtonElement = boost::shared_ptr<TextElement>(
        new TextElement(DisplayLayers::NORMAL, ResourceManager::getInstance().
        renderText("OK")));
    doneButtonElement->setPosition(WindowManager::getInstance().
        toScreenCoordinate(Common::Coordinate(10.25, 1.5)));

    highlightedSquareElement = boost::shared_ptr<SpriteElement>(
        new SpriteElement(DisplayLayers::HIGHLIGHTED,
        ResourceManager::getInstance().getSprite("BlackSquare")));
    addScreenElement(highlightedSquareElement);
}

void Tacticus::GameScreen::processInput()
{
    TacticusWindow &window = WindowManager::getInstance().getMainWindow();
    sf::Event event;
    while (window.pollEvent(event))
    {
        switch(event.type)
        {
        case sf::Event::Closed:
        {
            EventManager::getInstance().queueEvent(boost::shared_ptr<
                NewGameEvent>(new NewGameEvent()));
            break;
        }
        case sf::Event::MouseButtonPressed:
        {
            currentMousePosition = WindowManager::getInstance().getMainWindow()
                .convertCoords(sf::Vector2i(event.mouseButton.x,
                event.mouseButton.y));
            if (placingNow != NOBODY)
            {
                if (doneButtonElement->getBounds().contains(currentMousePosition
                    ))
                {
                    EventManager::getInstance().queueEvent(boost::shared_ptr<
                        PlacementDoneEvent>(new PlacementDoneEvent(placingNow)))
                        ;
                    break;
                }
            }
            if (event.mouseButton.button == sf::Mouse::Left)
            {
                initiateDrag();
            }
            break;
        }
        case sf::Event::MouseButtonReleased:
        {
            currentMousePosition = WindowManager::getInstance().getMainWindow()
                .convertCoords(sf::Vector2i(event.mouseButton.x,
                event.mouseButton.y));
            if (event.mouseButton.button == sf::Mouse::Left)
            {
                initiateDrop();
            }
            else if (placingNow != NOBODY &&
                event.mouseButton.button == sf::Mouse::Right)
            {
                EventManager::getInstance().fireEvent(boost::shared_ptr<
                    PlacementEvent>(new PlacementEvent(Field(NOBODY, EMPTY),
                    currentlyHighlightedField)));
                fillSidebar(placingNow);
            }
            break;
        }
        case sf::Event::MouseMoved:
        {
            currentMousePosition = WindowManager::getInstance().
                getMainWindow().convertCoords(sf::Vector2i(event.mouseMove.x,
                event.mouseMove.y));
            Common::Coordinate fieldPos = WindowManager::getInstance().
                toCoordinate(currentMousePosition);
            currentlyHighlightedField.x = (int) fieldPos.x;
            currentlyHighlightedField.y = (int) fieldPos.y;
            if (!hideBlackSquare())
            {
                highlightedSquareElement->setPosition(WindowManager::
                    getInstance().toScreenCoordinate(currentlyHighlightedField)
                    );
            }
            else
            {
                highlightedSquareElement->setPosition(WindowManager::
                    getInstance().toScreenCoordinate(Common::Coordinate()));
            }
            if (currentlyDraggedElement)
            {
                sf::Vector2f targetCord;
                sf::FloatRect bounds = currentlyDraggedElement->getBounds();
                targetCord.x = currentMousePosition.x - bounds.width / 2.f;
                targetCord.y = currentMousePosition.y - bounds.height / 2.f;
                currentlyDraggedElement->setPosition(targetCord);
            }

            break;
        }
        case sf::Event::KeyPressed:
        {
            if (event.key.control && event.key.code == sf::Keyboard::S &&
                placingNow != NOBODY)
            {
                EventManager::getInstance().queueEvent(boost::shared_ptr<
                    ShufflePlacementEvent>(new ShufflePlacementEvent(
                    placingNow)));
            }
            break;
        }
        default:
        {
            break;
        }
        }
    }
}

void Tacticus::GameScreen::handleEvent(boost::shared_ptr<Event> event)
{
    switch (event->getType())
    {
    case Events::STARTHOTSEAT_EVENT:
    {
        placingNow = RED;
        removeScreenElement(doneButtonElement);
        fillSidebar(RED);
        break;
    }
    case Events::SUCCESSFULPLACEMENT_EVENT:
    {
        boost::shared_ptr<SuccessfulPlacementEvent> succEv =
            boost::static_pointer_cast<SuccessfulPlacementEvent>(event);
        const Common::Coordinate &whereToPlace = succEv->getSuccessfulEvent()->
            getWhereToPlace();
        const Field &whatToPlace = succEv->getSuccessfulEvent()->getToPlace();
        if (boardElements[whereToPlace.x][whereToPlace.y])
        {
            removeScreenElement(boardElements[whereToPlace.x][whereToPlace.y]);
        }
        if (GameManager::getInstance().getGameBoard().areAllPlaced(placingNow))
        {
            addScreenElement(doneButtonElement);
        }
        else
        {
            removeScreenElement(doneButtonElement);
        }
        if (whatToPlace.getPiece() == EMPTY)
        {
            break;
        }
        fillSidebar(placingNow);
        boardElements[whereToPlace.x][whereToPlace.y] = boost::shared_ptr<
            SpriteElement>(new SpriteElement(DisplayLayers::NORMAL,
            ResourceManager::getInstance().getSprite(whatToPlace.str())));
        boardElements[whereToPlace.x][whereToPlace.y]->setPosition(
            WindowManager::getInstance().toScreenCoordinate(whereToPlace));
        addScreenElement(boardElements[whereToPlace.x][whereToPlace.y]);
        break;
    }
    case Events::SUCCESSFULPLACEMENTDONE_EVENT:
    {
        if (placingNow == RED && !NetworkManager::getInstance().isNetworked())
        {
            placingNow = BLUE;
            removeScreenElement(doneButtonElement);
            fillSidebar(BLUE);
            drawBoard();
        }
        else if (placingNow == BLUE && !NetworkManager::getInstance().
            isNetworked())
        {
            placingNow = NOBODY;
            removeScreenElement(doneButtonElement);
            removeScreenElement(sidebarGridElement);
            drawBoard();
            EventManager::getInstance().queueEvent(boost::shared_ptr<
                ShowHotseatSwitchDialogEvent>(new ShowHotseatSwitchDialogEvent()
                ));
        }
        break;
    }
    case Events::SUCCESSFULTURN_EVENT:
    {
        boost::shared_ptr<SuccessfulTurnEvent> successfulTurnEvent = boost::
            static_pointer_cast<SuccessfulTurnEvent>(event);
        const Common::CoordinateField &whereFrom =
            successfulTurnEvent->getWhereFrom();
        const Common::CoordinateField &whereTo =
            successfulTurnEvent->getWhereTo();
        removeScreenElement(boardElements[whereFrom.first.x]
            [whereFrom.first.y]);
        removeScreenElement(boardElements[whereTo.first.x][whereTo.first.y]);
        EventManager::getInstance().queueEvent(boost::shared_ptr<
            ShowDrawTurnScreenEvent>(new  ShowDrawTurnScreenEvent(
            whereFrom.first, whereTo.first, true)));
        EventManager::getInstance().queueEvent(boost::shared_ptr<
            ShowHotseatSwitchDialogEvent>(new ShowHotseatSwitchDialogEvent()));
        EventManager::getInstance().queueEvent(boost::shared_ptr<
            ShowDrawTurnScreenEvent>(new  ShowDrawTurnScreenEvent(
            whereFrom.first, whereTo.first)));
        drawBoard();
        break;
    }
    default:
    {
        throw Common::AssumptionException(
            "GameScreen got an unrecognized event");
    }
    }
}

void Tacticus::GameScreen::fillSidebar(Tacticus::Player whichColor)
{
    Common::ScreenCoordinate offset = WindowManager::getInstance().
        toScreenCoordinate(Common::Coordinate(10, 3));
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 6; j++)
        {
            if (GameManager::getInstance().getGameBoard().
                howManyToBePlaced(Field(whichColor, sidebarPieces[i][j])) > 0)
            {
                fillField(i, j, whichColor, sidebarPieces[i][j], sidebarElements
                    , false, offset);
            }
            else
            {
                removeScreenElement(sidebarElements[i][j]);
            }
        }
    }
}

void Tacticus::GameScreen::fillField(int fieldX, int fieldY,
    Tacticus::Player player, Tacticus::Piece piece, std::vector<std::vector<
    boost::shared_ptr<Tacticus::SpriteElement> > > &vect, bool small,
    Common::ScreenCoordinate drawOffsets)
{
    fillField(fieldX, fieldY, player, piece.str(), vect, small, drawOffsets);
}

void Tacticus::GameScreen::fillField(int fieldX, int fieldY,
    Tacticus::Player player, std::string piece, std::vector<std::vector<
    boost::shared_ptr<Tacticus::SpriteElement> > > &vect, bool small,
    Common::ScreenCoordinate drawOffsets)
{
    removeScreenElement(vect[fieldX][fieldY]);
    std::string resourceString = player.str() + piece;
    if (small)
        resourceString += ":Small";
    vect[fieldX][fieldY] = boost::shared_ptr<SpriteElement>(new SpriteElement(
        DisplayLayers::NORMAL, ResourceManager::getInstance().getSprite(
        resourceString)));
    Common::ScreenCoordinate drawTarget = WindowManager::getInstance().
        toScreenCoordinate(Common::Coordinate(fieldX, fieldY));
    drawTarget.x += drawOffsets.x;
    drawTarget.y += drawOffsets.y;
    vect[fieldX][fieldY]->setPosition(drawTarget);
    addScreenElement(vect[fieldX][fieldY]);
}

void Tacticus::GameScreen::drawBoard()
{
    for (int x = 0; x < 10; x++)
    {
        for(int y = 0; y < 10; y++)
        {
            const Field &field = GameManager::getInstance().getGameBoard().
                getFieldAt(Common::Coordinate(x, y));
            if (field.getPlayer() == NOBODY)
            {
                continue;
            }
            Player player;
            if (placingNow != NOBODY)
            {
                player = placingNow;
            }
            else
            {
                player = GameManager::getInstance().getGameBoard().
                    getCurrentPlayer();
            }
            if (field.getPlayer() != player)
            {
                fillField(x, y, field.getPlayer(), "Unknown", boardElements);
            }
            else
            {
                fillField(x, y, field.getPlayer(), field.getPiece(),
                    boardElements);
            }
        }
    }
}

bool Tacticus::GameScreen::hideBlackSquare() const
{
    if (currentlyHighlightedField.x > 9 && (!sidebarGridElement->getBounds().
        contains((sf::Vector2f)WindowManager::getInstance().toScreenCoordinate(
        currentlyHighlightedField)) || placingNow == NOBODY))
    {
        return true;
    }
    else if (Common::isOnTheBoard(currentlyHighlightedField) &&
        GameManager::getInstance().getGameBoard().getFieldAt(
        currentlyHighlightedField).getPiece() == IMPASSABLE)
    {
        return true;
    }
    else if ((placingNow == RED && currentlyHighlightedField.y < 6 &&
        currentlyHighlightedField.x < 10) || (placingNow == BLUE &&
        currentlyHighlightedField.y > 3 && currentlyHighlightedField.x < 10))
    {
        return true;
    }
    return false;
}

void Tacticus::GameScreen::initiateDrag()
{
    if (placingNow != NOBODY)
    {
        if (currentlyHighlightedField.x > 9 && sidebarGridElement->getBounds().
            contains((sf::Vector2f)currentMousePosition))
        {
            if (GameManager::getInstance().getGameBoard().howManyToBePlaced(
                Field(placingNow,
                sidebarPieces[currentlyHighlightedField.x - 10]
                [currentlyHighlightedField.y - 3])) == 0)
            {
                return;
            }
            currentlyDraggedField.first = currentlyHighlightedField;
            currentlyDraggedField.second = Field(placingNow, sidebarPieces
                [currentlyDraggedField.first.x - 10]
                [currentlyDraggedField.first.y - 3]);
            currentlyDraggedElement = boost::shared_ptr<SpriteElement>(
                new SpriteElement(DisplayLayers::FOREGROUND,
                ResourceManager::getInstance().getSprite(
                currentlyDraggedField.second.str())));
            sf::Vector2f targetCord;
            sf::FloatRect bounds = currentlyDraggedElement->getBounds();
            targetCord.x = currentMousePosition.x - bounds.width / 2.f;
            targetCord.y = currentMousePosition.y - bounds.height / 2.f;
            currentlyDraggedElement->setPosition(targetCord);
            addScreenElement(currentlyDraggedElement);
            removeScreenElement(sidebarElements
                [currentlyDraggedField.first.x - 10]
                [currentlyDraggedField.first.y - 3]);
        }
        else if (Common::isOnTheBoard(currentlyHighlightedField))
        {
            const Field &selectedField = GameManager::getInstance().
                getGameBoard().getFieldAt(currentlyHighlightedField);
            if (selectedField.getPlayer() != placingNow)
            {
                return;
            }
            currentlyDraggedField.first = currentlyHighlightedField;
            currentlyDraggedField.second = Field(selectedField);
            currentlyDraggedElement = boost::shared_ptr<SpriteElement>(
                new SpriteElement(DisplayLayers::FOREGROUND,
                ResourceManager::getInstance().getSprite(
                currentlyDraggedField.second.str())));
            sf::Vector2f targetCord;
            sf::FloatRect bounds = currentlyDraggedElement->getBounds();
            targetCord.x = currentMousePosition.x - bounds.width / 2.f;
            targetCord.y = currentMousePosition.y - bounds.height / 2.f;
            currentlyDraggedElement->setPosition(targetCord);
            addScreenElement(currentlyDraggedElement);
            EventManager::getInstance().queueEvent(boost::shared_ptr<
                PlacementEvent>(new PlacementEvent(Field(NOBODY, EMPTY),
                currentlyHighlightedField)));
        }
    }
    else
    {
        if (Common::isOnTheBoard(currentlyHighlightedField))
        {
            if (GameManager::getInstance().getGameBoard().getFieldAt(
                currentlyHighlightedField).getPlayer() != GameManager::
                getInstance().getGameBoard().getCurrentPlayer())
            {
                return;
            }
            currentlyDraggedField.first = currentlyHighlightedField;
            currentlyDraggedField.second = GameManager::getInstance().
                getGameBoard().getFieldAt(currentlyHighlightedField);
            currentlyDraggedElement = boost::shared_ptr<SpriteElement>(
                new SpriteElement(DisplayLayers::FOREGROUND,
                ResourceManager::getInstance().getSprite(
                currentlyDraggedField.second.str())));
            sf::Vector2f targetCord;
            sf::FloatRect bounds = currentlyDraggedElement->getBounds();
            targetCord.x = currentMousePosition.x - bounds.width / 2.f;
            targetCord.y = currentMousePosition.y - bounds.height / 2.f;
            currentlyDraggedElement->setPosition(targetCord);
            addScreenElement(currentlyDraggedElement);
            removeScreenElement(boardElements[currentlyHighlightedField.x]
                [currentlyHighlightedField.y]);
        }
    }
}

void Tacticus::GameScreen::initiateDrop()
{
    if (!currentlyDraggedElement)
        return;
    removeScreenElement(currentlyDraggedElement);
    currentlyDraggedElement = boost::shared_ptr<SpriteElement>();
    if (placingNow != NOBODY)
    {
        fillSidebar(placingNow);
        if (Common::isOnTheBoard(currentlyHighlightedField))
        {
            EventManager::getInstance().queueEvent(boost::shared_ptr<
                PlacementEvent>(new PlacementEvent(currentlyDraggedField.second,
                currentlyHighlightedField)));
        }
    }
    else
    {
        if (Common::isOnTheBoard(currentlyHighlightedField))
        {
            EventManager::getInstance().queueEvent(boost::shared_ptr<
                MakeTurnEvent>(new MakeTurnEvent(currentlyDraggedField.first,
                currentlyHighlightedField)));
        }
        addScreenElement(boardElements[currentlyDraggedField.first.x][
            currentlyDraggedField.first.y]);
    }
    currentlyDraggedField = std::pair<Common::Coordinate, Field>();
}
